package il.ac.mta.javaweb.ex1.logic;

import il.ac.mta.javaweb.ex1.logic.grids.BattleShipsGrid;
import il.ac.mta.javaweb.ex1.logic.players.ComputerPlayer;
import il.ac.mta.javaweb.ex1.logic.players.HumanPlayer;
import il.ac.mta.javaweb.ex1.logic.players.PlayerBase;

import java.awt.Point;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class BattleShipGameLoader
{
	private static final String TEN_STRING = "10";
	// Elements names
	private static final String PLAYER_ELEMENT_NAME = "player";
	private static final String BOAT_ELEMENT_NAME = "boat";
	private static final String BOARD_ELEMENT_NAME = "board";
	private static final String LOCATION_ELEMENT_NAME = "location";

	// Attributes names
	private static final String PLAYER_ATTRIBUTE_NAME = "name";
	private static final String PLAYER_ATTRIBUTE_TYPE = "type";

	private static final String BOAT_ATTRIBUTE_NAME = "name";
	private static final String BOAT_ATTRIBUTE_SIZE = "size";

	private static final String BOARD_ATTRIBUTE_PLAYER_NAME = "player";
	private static final String BOARD_LOCATION_ATTRIBUTE_NAME = "name";
	private static final String BOARD_LOCATION_ATTRIBUTE_POSITION = "position";
	private static final String BOARD_LOCATION_ATTRIBUTE_DIRECTION = "direction";

	// Values
	private static final String COMPUTER = "COMPUTER";
	private static final String HUMAN = "HUMAN";

	private static final String HORIZONTAL = "HORIZONTAL";
	private static final String VERTICAL = "VERTICAL";

	private static final String AIRCRAFT_CARRIER = "aircraft carrier";
	private static final String BATTLESHIP = "battleship";
	private static final String SUBMARINE = "submarine";
	private static final String CRUISER = "cruiser";
	private static final String DESTROYER = "destroyer";

	// Entities lists
	private static Hashtable<String, PlayerBase> players;

	public static List<PlayerBase> load(String path) throws ParserConfigurationException,
			IOException, Exception
	{
		players = new Hashtable<String, PlayerBase>();

		File gameInitXmlDataFile = new File(path);
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		// factory.setValidating(true);
		factory.setIgnoringComments(true);
		DocumentBuilder builder = factory.newDocumentBuilder();
		builder.setErrorHandler(new ErrorHandler()
		{
			String error = "Could not process the the game file";

			@Override
			public void warning(SAXParseException arg0) throws SAXException
			{
				throw new IllegalArgumentException(error);
			}

			@Override
			public void fatalError(SAXParseException arg0) throws SAXException
			{
				throw new IllegalArgumentException(error);
			}

			@Override
			public void error(SAXParseException arg0) throws SAXException
			{
				throw new IllegalArgumentException(error);
			}
		});

		Document doc = null;
		doc = builder.parse(gameInitXmlDataFile);

		doc.getDocumentElement().normalize();

		loadPlayers(doc);
		loadBoats(doc);
		loadBoards(doc);

		return new ArrayList<PlayerBase>(players.values());
	}

	private static void loadPlayers(Document doc) throws Exception
	{
		NodeList playersNodes = doc.getElementsByTagName(PLAYER_ELEMENT_NAME);
		for (int i = 0; i < playersNodes.getLength(); ++i)
		{
			Node playersNode = playersNodes.item(i);

			if (playersNode.getNodeType() == Node.ELEMENT_NODE)
			{
				Element playersElement = (Element) playersNode;

				String name = playersElement.getAttribute(PLAYER_ATTRIBUTE_NAME);
				String type = playersElement.getAttribute(PLAYER_ATTRIBUTE_TYPE);

				if (name == null || name.isEmpty())
				{
					throw new IllegalArgumentException("Player name cannot be empty");
				}

				if (!type.equals(HUMAN) && !type.equals(COMPUTER))
				{
					throw new IllegalArgumentException(String.format(
							"Player type %1s of player \"%2s\" is not a valid type.", type, name));
				}

				PlayerBase player;

				if (type.equals(COMPUTER))
				{
					player = new ComputerPlayer();
					player.setName(name);
					players.put(player.getName(), player);
				}
				else if (type.equals(HUMAN))
				{
					player = new HumanPlayer(name);
					players.put(player.getName(), player);
				}
			}
		}
	}

	private static void loadBoats(Document doc)
	{
		NodeList boatsNodes = doc.getElementsByTagName(BOAT_ELEMENT_NAME);

		for (int i = 0; i < boatsNodes.getLength(); ++i)
		{
			Node boatNode = boatsNodes.item(i);

			if (boatNode.getNodeType() == Node.ELEMENT_NODE)
			{
				Element boatElement = (Element) boatNode;

				String name = boatElement.getAttribute(BOAT_ATTRIBUTE_NAME);
				String size = boatElement.getAttribute(BOAT_ATTRIBUTE_SIZE);

				if (!name.equals(AIRCRAFT_CARRIER) && !name.equals(CRUISER)
						&& !name.equals(BATTLESHIP) && !name.equals(DESTROYER)
						&& !name.equals(SUBMARINE))
				{
					throw new IllegalArgumentException("Boat name cannot be empty");
				}

				if (!size.equals("2") && !size.equals("3") && !size.equals("4")
						&& !size.equals("5"))
				{
					throw new IllegalArgumentException(String.format(
							"Boat of size %1s is not a valid size.", size));
				}
			}
		}
	}

	private static int mapBoatNameToLength(String name)
	{
		if (name.equals(AIRCRAFT_CARRIER))
		{
			return 5;
		}
		else if (name.equals(BATTLESHIP))
		{
			return 4;
		}
		else if (name.equals(SUBMARINE))
		{
			return 3;
		}
		else if (name.equals(CRUISER))
		{
			return 3;
		}
		else if (name.equals(DESTROYER))
		{
			return 2;
		}

		throw new IllegalArgumentException(
				String.format("Boat name %1s is not a valid name.", name));
	}

	public static ShipDirection mapBoatDirection(String direction)
	{
		if (direction.equals(HORIZONTAL) || direction.toUpperCase().equals("H"))
		{
			return ShipDirection.HORIZONTAL;
		}
		else if (direction.equals(VERTICAL) || direction.toUpperCase().equals("V"))
		{
			return ShipDirection.VERTICAL;
		}

		throw new IllegalArgumentException(String.format(
				"Boat direction %1s is not a valid direction.", direction));
	}

	public static String mapBoatPositionToString(Point position)
	{
		char col = (char) ((char)position.x + 'A');
		String row = Integer.toString(position.y + 1);

		return col + row;
	}

	public static Point mapBoatPositionToPoint(String position)
	{
		boolean isValid = false;

		char col = 0;
		int row = -1;

		if (position.length() == 2 || position.length() == 3)
		{
			col = position.charAt(0);

			if (position.substring(1).equals(TEN_STRING))
			{
				row = 10;
			}
			else
			{
				row = (int) position.charAt(1) - '0';
			}

			// input is 1-10 but implementation is 0-9
			--row;
		}

		if (col >= 'A' && col <= 'J' && row >= 0 && row <= 9)
		{
			isValid = true;
		}

		if (!isValid)
		{
			return null;
		}
		else
		{
			return new Point((int) col - (int) 'A', row);
		}
	}

	private static void loadBoards(Document doc)
	{
		// Iterate boards
		NodeList boards = doc.getElementsByTagName(BOARD_ELEMENT_NAME);
		for (int i = 0; i < boards.getLength(); ++i)
		{
			Node boardNode = boards.item(i);

			// Parse board
			if (boardNode.getNodeType() == Node.ELEMENT_NODE)
			{
				Element boardElement = (Element) boardNode;

				String playerName = boardElement.getAttribute(BOARD_ATTRIBUTE_PLAYER_NAME);
				playerName = playerName.substring(0, 1).toUpperCase() + playerName.substring(1);

				if (players.get(playerName) == null)
				{
					throw new IllegalArgumentException(String.format(
							"Cannot create board for player %1s. Player does not exist in file.",
							playerName));
				}

				BattleShipsGrid grid = new BattleShipsGrid();
				PlayerBase player = players.get(playerName);
				player.setOwnGrid(grid);

				// Get all ships
				NodeList locations = boardElement.getElementsByTagName(LOCATION_ELEMENT_NAME);

				for (int j = 0; j < locations.getLength(); ++j)
				{
					Node locationNode = locations.item(j);

					if (locationNode.getNodeType() == Node.ELEMENT_NODE)
					{
						Element locationElement = (Element) locationNode;

						String name = locationElement.getAttribute(BOARD_LOCATION_ATTRIBUTE_NAME);
						String strPosition = locationElement
								.getAttribute(BOARD_LOCATION_ATTRIBUTE_POSITION);
						String strDirection = locationElement
								.getAttribute(BOARD_LOCATION_ATTRIBUTE_DIRECTION);

						int length = mapBoatNameToLength(name);
						Point topeLeftPos = mapBoatPositionToPoint(strPosition);
						ShipDirection direction = mapBoatDirection(strDirection);
						BattleShip ship = new BattleShip(length, topeLeftPos, direction);

						if (!grid.trySetShip(ship))
						{
							throw new IllegalArgumentException(
									String.format(
											"Cannot position ship %1s on board of player %2s in position of ",
											ship, player.getName(), strPosition));
						}
					}
				}
			}
		}
	}
}
